//
//  SLPagerReusePool.m
//  SLPagerView
//
//  Created by l.t.zero on 2022/1/7.
//

#import "SLPagerReusePool.h"
#import <objc/runtime.h>
#import "SLPagerHelper.h"

///复用相关
const char * const sl_pager_reuse = "sl_pager_reuse";

void sl_pager_reuse_in(id obj){
    if (!obj) return;
    objc_setAssociatedObject(obj, sl_pager_reuse, @(YES), OBJC_ASSOCIATION_COPY);
}
BOOL is_sl_pager_reuse_in(id obj){
    if (!obj) return NO;
    return [objc_getAssociatedObject(obj, sl_pager_reuse) boolValue];
}
void sl_pager_reuse_clear(id obj){
    if (!obj) return;
    objc_setAssociatedObject(obj, sl_pager_reuse, nil, OBJC_ASSOCIATION_COPY);
}

static id SL_instanceFromClass(Class cls, CGRect frame){
    id instance = [cls alloc];
    if ([instance isKindOfClass:[UITableViewCell class]]){
        instance = [instance initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@""];
    }else if ([instance isKindOfClass:[UIView class]]){
        instance = [instance initWithFrame:frame];
    }else{
        instance = [instance init];
    }
    return instance;
}

@interface SLPagerReusePool()

@property(nonatomic, strong) NSMapTable<NSString*, NSMapTable<NSIndexPath *, id> *> *table;//记录当前所有索引复用关系
@property(nonatomic, strong) NSMapTable *registerTable;
@property(nonatomic, strong) NSMapTable *prefetchTable;//记录当前缓冲区域索引
@property(nonatomic, strong) NSMapTable *lruTable;//记录当前复用View复用次数

@end

@implementation SLPagerReusePool

- (instancetype)init{
    
    if (self = [super init]){
        self.depth = 2;
        _table = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory];
        _registerTable = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory];
        _prefetchTable = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsWeakMemory valueOptions:NSPointerFunctionsStrongMemory];
        _lruTable = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsWeakMemory valueOptions:NSPointerFunctionsStrongMemory];

    }
    
    return self;;
}

#pragma mark -- Reuse
- (void)registerClass:(Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier{
    [self.registerTable setObject:NSStringFromClass(cellClass) forKey:identifier];
}

- (__kindof UIView *)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath{
    
    NSMapTable *table = [self tableForIdentifier:identifier];
    UIView *view = [table objectForKey:indexPath];
    if (view == nil){
        ///直接定位查找不符合，即开始根据LRU算法查找
        view = [self viewByLru:table forIndexPath:indexPath];
        if (view == nil){
            ///LRU查找不符合，创建新的复用
            NSString * clsname = [self.registerTable objectForKey:identifier];
            view = SL_instanceFromClass(NSClassFromString(clsname), self.preferredFrame);
            [table setObject:view forKey:indexPath];
        }
    }
    for (NSMapTable *tb in self.table.objectEnumerator){
        if (tb == table) continue;
        UIView *v = [table objectForKey:indexPath];
        if (v){
            sl_pager_reuse_clear(v);
            [tb removeObjectForKey:indexPath];
        }
       
    }
    [table setObject:view forKey:indexPath];
    sl_pager_reuse_in(view);
    [self visitReuseView:view];

    return view;
}

- (NSMapTable *)tableForIdentifier:(NSString *)identifier{
    NSMapTable *table = [self.table objectForKey:identifier];
    if (!table){
        table = [NSMapTable mapTableWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsStrongMemory];
        [self.table setObject:table forKey:identifier];
    }
    return table;
}
- (UIView *)clearReuseFor:(NSIndexPath *)indexPath{
    UIView *view = [self reuseViewFor:indexPath];
    sl_pager_reuse_clear(view);
    return view;
}
- (UIView *)reuseViewFor:(NSIndexPath *)indexPath{
    for (NSMapTable *table in self.table.objectEnumerator){
        UIView *view = [table objectForKey:indexPath];
        if (view){
            return view;
        }
    }
    return nil;
}
- (NSIndexPath *)indexPathForReuseView:(UIView *)view{
    for (NSMapTable *table in self.table.objectEnumerator){
        for (id key in table){
            if (view == [table objectForKey:key] && [self.prefetchTable objectForKey:key] != nil){
                return key;
            }
        }
    }
    return nil;
}
#pragma mark -- Prefetch

- (void)prefetchByVisibleIndexPaths:(NSArray<NSIndexPath *> *)ips limited:(NSInteger)limited{
    
    if (self.disablePrefetched) return;
    
    NSInteger min = [ips firstObject].row;
    NSInteger max = [ips lastObject].row;
    
    NSMutableArray *arr = [NSMutableArray arrayWithArray:ips];
    for (NSInteger i = 1; i <= self.depth; i++){
        if (max + i < limited){
            [arr addObject:[NSIndexPath indexPathForRow:max+i inSection:0]];
        }else if(self.enableInfinateScroll){
            [arr addObject:[NSIndexPath indexPathForRow:(min+i)%limited inSection:0]];
        }
        
        if (min - i >= 0){
            [arr addObject:[NSIndexPath indexPathForRow:min - i inSection:0]];
        }else if(self.enableInfinateScroll){
            [arr addObject:[NSIndexPath indexPathForRow:min - i + limited inSection:0]];
        }
    }
    NSSet *set = [NSSet setWithArray:arr];
    //预加载范围
    [sl_array_filter(set.allObjects, ^BOOL(id  _Nonnull obj) {
        return ![[self.prefetchTable objectForKey:obj] boolValue];
    }) enumerateObjectsUsingBlock:^(NSIndexPath*  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        //预加载
        if ([self.delegate respondsToSelector:@selector(sl_pagerPrefetchAtIndexPath:)]){
            [self.delegate sl_pagerPrefetchAtIndexPath:obj];
        }
        [self.prefetchTable setObject:@(YES) forKey:obj];
    }];
    
    NSMutableArray *tmp = [NSMutableArray new];
    for (id key in self.prefetchTable.keyEnumerator){
        if (![arr containsObject:key]){
            [tmp addObject:key];
        }
    }
    [tmp enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        [self.prefetchTable removeObjectForKey:obj];
        sl_pager_reuse_clear([self reuseViewFor:obj]);
        if ([self.delegate respondsToSelector:@selector(sl_pagerPrefetchDidCancelled:)]){
            [self.delegate sl_pagerPrefetchDidCancelled:obj];
        }
    }];
}
- (BOOL)isPrefetchedFor:(NSIndexPath *)indexPath{
    return [[self.prefetchTable objectForKey:indexPath] boolValue];
}

- (void)clear{
    for (NSMapTable *table in self.table.objectEnumerator){
        [table removeAllObjects];
    }
    [self.table removeAllObjects];
    [self invaliteAllReuse];
}
- (void)prefetchedFor:(NSIndexPath *)indexPath{
    [self.prefetchTable setObject:@(YES) forKey:indexPath];
}
- (void)clearPrefetchedFor:(NSIndexPath *)indexPath{
    [self.prefetchTable removeObjectForKey:indexPath];
}
- (void)clearPrefetchedFrom:(NSIndexPath *)indexPath{
    NSMutableArray *arr = [NSMutableArray new];
    for (NSIndexPath* key in self.prefetchTable){
        if (key.row >= indexPath.row){
            [arr addObject:key];
        }
    }
    [arr enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        [self.prefetchTable removeObjectForKey:obj];
    }];
}
- (void)invaliteAllReuse{
    [self clearAllPrefetched];
    
    for (id view in self.lruTable.keyEnumerator){
        sl_pager_reuse_clear(view);
    }
}
- (void)clearAllPrefetched{
    [self.prefetchTable removeAllObjects];
}
- (void)clearInUse:(UIView *)view{
    sl_pager_reuse_clear(view);
}
- (void)reuse:(UIView *)view{
    sl_pager_reuse_in(view);
}
#pragma mark -- LRU
- (UIView *)viewByLru:(NSMapTable *)table forIndexPath:(NSIndexPath *)indexPath{
    NSMutableSet *set = [NSMutableSet new];
    
    NSInteger count = self.depth * 2 + 2;
    //遍历看起来可复用view
    for (NSIndexPath *key in table){
        UIView *view = [table objectForKey:key];
        if (is_sl_pager_reuse_in(view)) continue;
        if (labs(key.row - indexPath.row) < count) continue;
        
        [set addObject:view];

    }
    //根据当前所有view，校验index
    NSArray *views = sl_array_filter(set.allObjects, ^BOOL(id  _Nonnull obj) {
        for (NSIndexPath* key in table){
            id v = [table objectForKey:key];
            if (v == obj && labs(key.row - indexPath.row) < 2 + self.depth * 2){
                return NO;
            }
        }
        return YES;
    });
    NSArray *sortArray =  [views sortedArrayUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
        return [self visitTimesFor:obj1] < [self visitTimesFor:obj2]?NSOrderedAscending:NSOrderedDescending;
    }];

    return  [sortArray firstObject];
}
- (NSInteger)visitTimesFor:(UIView *)view{
    return [[self.lruTable objectForKey:view] intValue];
}
- (void)visitReuseView:(UIView *)view{
    NSInteger times = [[self.lruTable objectForKey:view] intValue];
    [self.lruTable setObject:@(times + 1) forKey:view];
}

- (void)updateIndexPath:(NSIndexPath* (^)(NSIndexPath *indexPath))block{
    for (NSMapTable* table in self.table.objectEnumerator){
        [self updateTable:table block:block];
    }
    [self updateTable:self.prefetchTable block:block];
}
- (void)updateTable:(NSMapTable<NSIndexPath*, id> *)table block:(NSIndexPath* (^)(NSIndexPath *indexPath))block{
    NSMutableArray *array = [NSMutableArray new];
    for (id key in table){
        if (block){
            NSIndexPath *to = block(key);
            NSIndexPath *from = key;
            [array addObject:@[from, to]];
        }
    }
    for (NSArray *tmp in array){
        [table setObject:[table objectForKey:tmp.firstObject] forKey:tmp.lastObject];
        [table removeObjectForKey:tmp.firstObject];
    }
}

#pragma mark -- addition
- (NSString *)debugDescription{
    NSMutableArray *all = [NSMutableArray new];
    for (id key in self.prefetchTable){
        if ([[self.prefetchTable objectForKey:key] boolValue]){
            [all addObject:key];
        }
    }
    NSArray *tmp = [sl_array_map(all, ^id _Nullable(NSIndexPath*  _Nonnull obj) {
        return @(obj.row);
    }) sortedArrayUsingComparator:^NSComparisonResult(id  _Nonnull obj1, id  _Nonnull obj2) {
        return [obj1 compare:obj2];
    }];
    
    
    NSMutableString *mStr = [[NSMutableString alloc] initWithString:@"--------\n复用状态:\n"];
    for (NSString *key in self.table){
        NSMapTable *table = [self.table objectForKey:key];
        NSMutableSet *set = [[NSMutableSet alloc] init];
        for (id obj in table.objectEnumerator){
            [set addObject:obj];
        }
        NSString *str = [NSString stringWithFormat:@"%@ reuse %ld view\n",key, set.count];
        [mStr appendString:str];
    }
    [mStr appendString:@"--------\n"];
    
    return [NSString stringWithFormat:@"%@:%p\n当前预加载索引[%@]\n%@", NSStringFromClass([self class]),self, [tmp componentsJoinedByString:@","], mStr];
}
@end
